// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analysis_server/src/services/correction/fix/data_driven/accessor.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';

/// Use a specified argument from an invocation as the value of a template
/// variable.
class CodeFragment extends ValueGenerator {
  /// The accessor used to access the code fragment.
  final List<Accessor> accessors;

  /// Initialize a newly created extractor to extract a code fragment.
  CodeFragment(this.accessors);

  @override
  String evaluateIn(TemplateContext context) {
    Object? target = context.node;
    for (var i = 0; i < accessors.length; i++) {
      var result = accessors[i].getValue(target);
      if (!result.isValid) {
        return '';
      }
      target = result.result;
    }
    if (target is AstNode) {
      return context.utils.getRangeText(range.node(target));
    } else if (target is DartType) {
      // TODO(brianwilkerson): If we end up needing it, figure out how to convert
      //  a type into valid code.
      throw UnsupportedError('Unexpected result of ${target.runtimeType}');
    } else {
      throw UnsupportedError('Unexpected result of ${target.runtimeType}');
    }
  }

  @override
  String toString() => accessors.join('.');

  @override
  bool validate(TemplateContext context) {
    Object? target = context.node;
    for (var accessor in accessors) {
      var result = accessor.getValue(target);
      if (!result.isValid) {
        return false;
      }
      target = result.result;
    }
    return true;
  }

  @override
  void writeOn(DartEditBuilder builder, TemplateContext context) {
    Object? target = context.node;
    for (var accessor in accessors) {
      target = accessor.getValue(target).result;
    }
    if (target is AstNode) {
      builder.write(context.utils.getRangeText(range.node(target)));
    } else if (target is DartType) {
      builder.writeType(target);
    } else {
      throw UnsupportedError('Unexpected result of ${target.runtimeType}');
    }
  }
}

/// Use a name that might need to be imported from a different library as the
/// value of a template variable.
class ImportedName extends ValueGenerator {
  /// The URIs of the libraries from which the name can be imported.
  final List<Uri> uris;

  /// The name to be used.
  final String name;

  ImportedName(this.uris, this.name);

  @override
  String evaluateIn(TemplateContext context) {
    return name;
  }

  @override
  bool validate(TemplateContext context) {
    // TODO(brianwilkerson): Validate that the import can be added.
    return true;
  }

  @override
  void writeOn(DartEditBuilder builder, TemplateContext context) {
    builder.writeImportedName(uris, name);
  }
}

/// An object used to generate the value of a template variable.
abstract class ValueGenerator {
  /// Return the value generated by this generator, using the [context] to
  /// access needed information that isn't already known to this generator.
  String evaluateIn(TemplateContext context);

  /// Use the [context] to validate that this generator will be able to generate
  /// a value.
  bool validate(TemplateContext context);

  /// Write the value generated by this generator to the given [builder], using
  /// the [context] to access needed information that isn't already known to
  /// this generator.
  void writeOn(DartEditBuilder builder, TemplateContext context);
}
